有了前面Expression版本概念後,接著可以進到Dapper底層最核心的技術 : Emit。
首先要有個概念,MSIL(CIL)目的是給JIT編譯器看的,所以可讀性會很差、難Debug,但比起Expression來說可以做到更細節的邏輯操作。
在實際環境開發使用Emit,一般會先寫好C#代碼後 > 反編譯查看IL > 使用Emit建立動態方法
,舉例 :
1.首先建立一個簡單打印例子 :
void SyaHello()
{
Console.WriteLine("Hello World");
}
2.反編譯查看IL
SyaHello:
IL_0000: nop
IL_0001: ldstr "Hello World"
IL_0006: call System.Console.WriteLine
IL_000B: nop
IL_000C: ret
3.使用DynamicMethod + Emit建立動態方法
void Main()
{
// 1. 建立 void 方法()
DynamicMethod methodbuilder = new DynamicMethod("Deserialize" + Guid.NewGuid().ToString(),typeof(void),null);
// 2. 建立方法Body內容,藉由Emit
var il = methodbuilder.GetILGenerator();
il.Emit(OpCodes.Ldstr, "Hello World");
Type[] types = new Type[1]
{
typeof(string)
};
MethodInfo method = typeof(Console).GetMethod("WriteLine", types);
il.Emit(OpCodes.Call,method);
il.Emit(OpCodes.Ret);
// 3. 轉換指定類型的Func or Action
var action = (Action)methodbuilder.CreateDelegate(typeof(Action));
action();
}
但是對已經寫好的專案來說就不是這樣流程了,開發者不一定會好心的告訴你當初設計的邏輯,所以接著討論此問題。
我的解決方式是 : 「既然只有Runtime才能知道IL,那麼將IL保存成靜態檔案再反編譯查看」
這邊可以使用MethodBuild + Save
方法將IL保存成靜態exe檔案 > 反編譯查看
,但需要特別注意
region if 指定版本
來做區分,否則不能使用,如圖片代碼如下 :
//使用MethodBuilder查看別人已經寫好的Emit IL
//1. 建立MethodBuilder
AppDomain ad = AppDomain.CurrentDomain;
AssemblyName am = new AssemblyName();
am.Name = "TestAsm";
AssemblyBuilder ab = ad.DefineDynamicAssembly(am, AssemblyBuilderAccess.Save);
ModuleBuilder mb = ab.DefineDynamicModule("Testmod", "TestAsm.exe");
TypeBuilder tb = mb.DefineType("TestType", TypeAttributes.Public);
MethodBuilder dm = tb.DefineMethod("TestMeThod", MethodAttributes.Public |
MethodAttributes.Static, type, new[] { typeof(IDataReader) });
ab.SetEntryPoint(dm);
// 2. 填入IL代碼
//..略
// 3. 生成靜態檔案
tb.CreateType();
ab.Save("TestAsm.exe");
接著使用此方式在GetTypeDeserializerImpl方法反編譯Dapper Query Mapping IL,可以得出C#代碼 :
public static User TestMeThod(IDataReader P_0)
{
int index = 0;
User user = new User();
object value = default(object);
try
{
User user2 = user;
index = 0;
object obj = value = P_0[0];
if (!(obj is DBNull))
{
user2.Name = (string)obj;
}
index = 1;
object obj2 = value = P_0[1];
if (!(obj2 is DBNull))
{
user2.Age = (int)obj2;
}
user = user2;
return user;
}
catch (Exception ex)
{
SqlMapper.ThrowDataException(ex, index, P_0, value);
return user;
}
}
有了C#代碼後再來了解Emit邏輯會快很多,接著就可以進到Emit版本Query實作部分。